import { reactive, watch } from "vue";
import { forEachValue, isPromise } from './utils';
import { storeKey } from './injectKey';
import ModuleCollection from "./module/module-collection";

// 根据path获取根节点上响应式的state
function getNestedState(state, path) {
  return path.reduce((state, key) => state[key], state)
}

// 把状态定义到store.state上,递归安装
function installModule(store, rootState, path, module) {
  // 空数组就说明是根,否则就不是
  const isRoot = !path.length;
  // 计算命名空间
  const namespace = store._modules.getNamespaced(path);
  if (!isRoot) {
    // 找到父state
    const parentState = path.slice(0, -1).reduce((state, key) => state[key], rootState);
    // 处理state
    store._withCommit(() => {
      parentState[path[path.length - 1]] = module.state;
    });
  }
  // 遍历子节点
  // 处理getter
  module.forEachGetter((getter, key) => {
    // 如果有命名空间就把这个前缀加上
    store._wrappedGetters[namespace + key] = () => {
      // getter的参数不能直接用module.state,因为不是响应式的,所以要靠方法取根上的
      return getter(getNestedState(store.state, path));
    }
  });
  // 处理mutation
  module.forEachMutation((mutation, key) => {
    // 如果第一次没值就构建个数组
    const entry = store._mutations[namespace + key] || (store._mutations[namespace + key] = []);
    // 方法push进去,payload是commit传的参数
    entry.push((payload) => {
      mutation.call(store, getNestedState(store.state, path), payload)
    });
  });
  // mutation与action区别,action执行后会返回promise
  // 处理action
  module.forEachAction((action, key) => {
    // 如果第一次没值就构建个数组
    const entry = store._actions[namespace + key] || (store._actions[namespace + key] = []);
    // 方法push进去,payload是dispatch传的参数
    entry.push((payload) => {
      // res有可能不是promise
      const res = action.call(store, store, payload);
      // 如果res不是promise就包一个promise,不然就不用包了
      if (!isPromise(res)) {
        return Promise.resolve(res);
      } else {
        return res;
      }
    });
  });
  // 处理module
  module.forEachChild((child, key) => {
    installModule(store, rootState, path.concat(key), child);
  });
}
// 定义响应式数据
function resetStoreState(store, state) {
  // 保存 state,reactive包一下就是响应式了,加个data是为了方便赋值
  store._state = reactive({ data: state });
  store.getters = Object.create(null);
  const wrappedGetters = store._wrappedGetters;
  // 处理getter,把每一个方法结果存在getters上
  forEachValue(wrappedGetters, (getter, key) => {
    Object.defineProperty(store.getters, key, {
      // getter第一个参数是state
      // vuex4的getter无法用计算属性,因此不会被缓存,因为组件销毁时会将会将计算属性一起销毁,从而失去这个getter
      // vuex3.2版本还没解决F
      get: () => getter(state),
      // 可以枚举
      enumerable: true
    });
  });
  // 如果有严格模式就开启
  if (store.strict) {
    enableStrictMode(store);
  }
}
function enableStrictMode(store) {
  // 监听state,深度监听,默认是异步的,改成同步的
  watch(() => store._state.data, () => {
    // 如果是_committing是false,就说明不是在mutation中提交修改的
    console.assert(store._committing, "不建议不通过mutation直接修改属性");
  }, { deep: true, flush: "sync" })
}
export default class Store {
  // 在commit时放行,防止严格模式报错
  _withCommit(fn) {
    // 保存下原来的
    const committing = this._committing;
    this._committing = true;
    fn();
    this._committing = committing;
  }
  constructor(options) {
    const store = this;
    // 是否严格模式，默认不开起
    store.strict = options.strict || false;
    // mutation是同步代码，如果异步执行则在严格模式报错
    // 在调mutation前改成true,为false时如果数据发生改变就报提示
    store._committing = false;
    // 树型格式化用户数据
    store._modules = new ModuleCollection(options);
    store._wrappedGetters = Object.create(null);
    store._mutations = Object.create(null);
    store._actions = Object.create(null);
    // 定义状态
    const state = store._modules.root.state; // 根状态
    // 把状态定义到 store._modules.root.state.aCount.bCount.count
    installModule(store, state, [], store._modules.root);
    // 定义响应式数据
    resetStoreState(store, state);
    // 记录订阅
    store._subscribes = [];
    // 依次执行plugin
    options.plugins && options.plugins.forEach((plugin) => plugin(store));
  }
  // 添加订阅
  subscribe(fn) {
    this._subscribes.push(fn);
  };
  // 直接替换state
  replaceState(newState) {
    // 严格模式不能直接修改状态，要包一下
    this._withCommit(() => {
      this._state.data = newState;
    });
  }
  // 拦截get,返回真正的state
  get state() {
    return this._state.data;
  }
  // 触发mutations的方法,type:类型,payload:参数
  // 要用箭头函数稳住this指向,不然解构的话就乱了
  commit = (type, payload) => {
    // 发布订阅循环执行
    const entry = this._mutations[type] || [];
    this._withCommit(() => {
      entry.forEach(handler => handler((payload)));
    });
    // 遍历触发订阅
    this._subscribes.forEach(sub => sub({ type, payload }, this._state.data));
  }
  // 提交actions的方法,type:类型,payload:参数
  // 要用箭头函数稳住this指向,不然解构的话就乱了
  dispatch = (type, payload) => {
    // 发布订阅循环执行
    const entry = this._actions[type] || [];
    return Promise.all(entry.map(handler => handler((payload))));
  }
  // vue.use会自动调用此方法,app(是vue实例),injectKey是store的名字可以不传
  install(app, injectKey = storeKey) {
    // 把这个store实例注入到app中,暴露成全局变量,第一个参数类似key,第二个类型value
    app.provide(injectKey, this);
    // 为了$store能用,把store暴露到app上,类似以前 Vue.prototype.$store = this;
    app.config.globalProperties.$store = this;
  }
  // 动态添加store,path["父级名字","自己名字"]
  registerModule(path, rawModule) {
    const store = this;
    // 如果是字符串就转成数组
    if (typeof path === 'string') path = [path];
    // 原有的模块上加一个module
    const newModule = store._modules.register(path, rawModule);
    // 安装module
    installModule(store, store.state, path, newModule);
    // 重新定义响应式数据
    resetStoreState(store, store.state);
  }
}